using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Cmpp.Client.Cmpp3Message;
using Cmpp.Client.Cmpp3Message.Utility;

namespace Cmpp.Client {
    public class Cmpp3Client {
        #region private fields.

        private readonly int READ_WRITE_BUFFER_LENGTH = 1024;
        private readonly int SOCKET_READ_WRITE_POOL_CAPACITY = 5;
        private readonly BufferManager m_buffer;
        private readonly object m_readSyncLock = new object();
        private readonly SocketAsyncEventArgsPool m_readwirtePool;
        private readonly object m_receiveSyncLock = new object();
        private readonly int ACTIVETIME_INTERVAL = 20 * 1000;
        private readonly Timer activeTestTimer;
        private bool m_executingReceiveAsync;
        private bool m_isLogin;
        private Socket m_socket;

        #endregion

        public Cmpp3Client() {
            m_buffer = new BufferManager(READ_WRITE_BUFFER_LENGTH * 10);

            // init the read-write pool.
            m_readwirtePool = new SocketAsyncEventArgsPool(SOCKET_READ_WRITE_POOL_CAPACITY);
            for (var i = 0; i < SOCKET_READ_WRITE_POOL_CAPACITY; i++) {
                var buffer = new byte[READ_WRITE_BUFFER_LENGTH];
                var readWriteAsync = new SocketAsyncEventArgs();
                readWriteAsync.SetBuffer(buffer, 0, buffer.Length);
                readWriteAsync.Completed += ReadWriteAsyncCompleted;

                m_readwirtePool.Push(readWriteAsync);
            }

            activeTestTimer = new Timer(activeTestTimerCallback, null, Timeout.Infinite, Timeout.Infinite);

            InitializeInternalEvent();
        }

        private void InitializeInternalEvent() {
            ActiveTestResponsed += ActiveTestResponsedEventInternal;
            SubmitResponsed += SubmitResponsedEventInternal;
            DeliverRequested += DeliverRequestedEventInternal;
            TerminateResponsed += TerminateResponsedEventInternal;
        }

        #region public members.

        public event EventHandler<Cmpp3ResponseEventArgs<Cmpp3ActiveTestResponse>> ActiveTestResponsed;
        public event EventHandler<Cmpp3ResponseEventArgs<Cmpp3SubmitResponse>> SubmitResponsed;
        public event EventHandler<Cmpp3ResponseEventArgs<Cmpp3DeliverRequest>> DeliverRequested;
        public event EventHandler<Cmpp3ResponseEventArgs<Cmpp3TerminateResponse>> TerminateResponsed;
        public event EventHandler<SocketAsyncEventArgs> SocketError;

        public bool Connected {
            get {
                if (m_socket == null) {
                    return false;
                }

                return m_socket.Connected;
            }
        }

        public bool IsLogin {
            get {
                if (!Connected) {
                    return false;
                }
                return m_isLogin;
            }
        }

        public void Connect(string address, int port) {
            IPAddress ipAddress = IPAddress.Parse(address);
            Connect(ipAddress, port);
        }

        public void Connect(IPAddress address, int port) {
            // create socket if null.
            if (m_socket == null) {
                m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            }

            // close connect if it was opend.
            if (Connected) {
                m_socket.Close();
            }

            Debug.WriteLine("socket.connect");
            // connect server.
            m_socket.Connect(address, port);
        }

        public void Close() {
            if (Connected) {
                m_socket.Shutdown(SocketShutdown.Both);
                m_socket.Close();
            }
        }

        public bool Login(string spId, string password) {
            if (m_isLogin) {
                return true;
            }

            // create cmpp_connect request.
            var connectRequest = new Cmpp3ConnectRequest(spId, password);

            Debug.WriteLine("\n" + connectRequest, "cmpp3.connect.request");
            // send request.
            m_socket.Send(connectRequest.ToBytes());

            var bufferResponse = new byte[33];
            m_socket.Receive(bufferResponse);

            var response = Cmpp3ResponseFactory.GetResponse(bufferResponse) as Cmpp3ConnectResponse;

            Debug.WriteLine("\n" + response, "cmpp3.connect.response");
            if (response == null) {
                Debug.WriteLine("cmpp3.connect.error --- parse data format error.");
                return false;
            }

            if (response.Status != 0) {
                Debug.WriteLine(response.Status, "cmpp3.connect.failed");
                return false;
            }

            m_isLogin = true;
            activeTestTimer.Change(ACTIVETIME_INTERVAL, ACTIVETIME_INTERVAL);
            Debug.WriteLine("cmpp3.connect.success");

            // receive data async.
            ReceiveAsync();
            return true;
        }

        public bool Logout() {
            if (!m_isLogin) {
                return true;
            }

            // create cmpp_connect request.
            var terminateRequest = new Cmpp3TerminateRequest();

            Debug.WriteLine("\n" + terminateRequest, "cmpp3.connect.terminate");
            // send terminate.
            byte[] data = terminateRequest.ToBytes();

            SendAsync(data);

            return true;
        }

        public void Submit(Cmpp3SubmitRequest submitRequest) {
            SendAsync(submitRequest.ToBytes());

            Debug.WriteLine("\n" + submitRequest, "cmpp3.submit.request");
        }

        #endregion

        private void SendActiveTestRequest() {

            var request = new Cmpp3ActiveTestRequest();

            SendAsync(request.ToBytes());

            Debug.WriteLine("\n" + request, "cmpp3.activetest.request");
        }

        private void ReadWriteAsyncCompleted(object sender, SocketAsyncEventArgs readWriteAsync) {
            if (readWriteAsync.SocketError != System.Net.Sockets.SocketError.Success) {
                if (SocketError != null) {
                    SocketError(sender, readWriteAsync);
                }

                Debug.WriteLine(readWriteAsync.SocketError, "ReadWriteAsyncCompleted_SocketError");

                return;
            }

            activeTestTimer.Change(ACTIVETIME_INTERVAL, ACTIVETIME_INTERVAL);

            switch (readWriteAsync.LastOperation) {
                case SocketAsyncOperation.Receive:
                    lock (m_readSyncLock) {
                        ProcessReceivedData(readWriteAsync);
                    }

                    if (!m_executingReceiveAsync) {
                        lock (m_receiveSyncLock) {
                            if (!m_executingReceiveAsync) {
                                ReceiveAsync();
                            }
                        }
                    }
                    break;
                case SocketAsyncOperation.Send:
                    m_readwirtePool.Push(readWriteAsync);
                    break;
            }
        }

        private void ProcessReceivedData(SocketAsyncEventArgs readAsync) {
            int count = readAsync.BytesTransferred;
            if (count > 0) {
                m_buffer.WriteBuffer(readAsync.Buffer, 0, count);
                byte[] packet = m_buffer.ReadPacket();
                if (packet.Length != 0) {
                    Cmpp3Response response = Cmpp3ResponseFactory.GetResponse(packet);

                    DistributeReceivedMessage(response);
                }
            }

            m_readwirtePool.Push(readAsync);
            m_executingReceiveAsync = false;
        }

        private void DistributeReceivedMessage(Cmpp3Response response) {
            switch (response.Header.Command) {
                case Cmpp3Command.ActiveTestResponse:
                    if (ActiveTestResponsed != null) {
                        var activeTestEventArgs = new Cmpp3ResponseEventArgs<Cmpp3ActiveTestResponse>(response);
                        ActiveTestResponsed.Invoke(this, activeTestEventArgs);
                    }
                    break;
                case Cmpp3Command.SubmitResponse:
                    if (SubmitResponsed != null) {
                        var submitEventArgs = new Cmpp3ResponseEventArgs<Cmpp3SubmitResponse>(response);
                        SubmitResponsed.Invoke(this, submitEventArgs);
                    }
                    break;

                case Cmpp3Command.DeliverRequest:
                    if (DeliverRequested != null) {
                        var deliverEventArgs = new Cmpp3ResponseEventArgs<Cmpp3DeliverRequest>(response);
                        DeliverRequested.Invoke(this, deliverEventArgs);
                    }
                    break;

                case Cmpp3Command.TerminateResponse:
                    if (TerminateResponsed != null) {
                        var terminateEventArgs = new Cmpp3ResponseEventArgs<Cmpp3TerminateResponse>(response);
                        TerminateResponsed.Invoke(this, terminateEventArgs);
                    }
                    break;

                default:
                    throw new NotSupportedException(response.Header.Command.ToString());
            }
        }

        private void SendAsync(byte[] data) {
            if (!Connected) {
                return;
            }

            SocketAsyncEventArgs writeAsync = m_readwirtePool.Pop();

            if (writeAsync == null) {
                throw new NullReferenceException("writeAsync");
            }

            writeAsync.SetBuffer(data, 0, data.Length);
            if (!m_socket.SendAsync(writeAsync)) {
                //ProcessSendingData(writeAsync);
            }
        }

        private void ReceiveAsync() {
            if (!Connected) {
                return;
            }

            if (m_executingReceiveAsync) {
                return;
            }

            lock (m_receiveSyncLock) {
                if (m_executingReceiveAsync) {
                    return;
                }
            }

            m_executingReceiveAsync = true;
            var readAsync = m_readwirtePool.Pop();
            var raiseEvent = m_socket.ReceiveAsync(readAsync);
            if (!raiseEvent) {
                ProcessReceivedData(readAsync);
            }

            // reset the active test timer.
            activeTestTimer.Change(ACTIVETIME_INTERVAL, ACTIVETIME_INTERVAL);
        }

        private void activeTestTimerCallback(object state) {
            if (Connected) {
                SendActiveTestRequest();
            }
        }

        private static void SubmitResponsedEventInternal(object sender, Cmpp3ResponseEventArgs<Cmpp3SubmitResponse> e) {
            Debug.WriteLine("\n" + e.Response, "cmpp3.submit.response");
        }

        private void DeliverRequestedEventInternal(object sender, Cmpp3ResponseEventArgs<Cmpp3DeliverRequest> e) {
            Debug.WriteLine("cmpp3.deliver.request");

            var deliverResponse = new Cmpp3DeliverResponse { MessageId = e.Response.MessageId, Result = 0 };
            SendAsync(deliverResponse.ToBytes());
        }

        private static void TerminateResponsedEventInternal(object sender, Cmpp3ResponseEventArgs<Cmpp3TerminateResponse> e) {
            Debug.WriteLine("cmpp3.terminate.response");
        }

        private static void ActiveTestResponsedEventInternal(object sender, Cmpp3ResponseEventArgs<Cmpp3ActiveTestResponse> e) {
            Debug.WriteLine("\n" + e.Response, "cmpp3.activetest.response");
        }

    }
}